package apiserver

import (
	"crypto/md5"
	"encoding/hex"
	"git.hortorgames.com/orange/tang/config"
	"git.hortorgames.com/orange/tang/data"
	"git.hortorgames.com/orange/tang/env"
	"git.hortorgames.com/orange/tang/http"
	"git.hortorgames.com/orange/tang/utils"
	"git.hortorgames.com/serverless/htgserver/apiserver/session"
	"git.hortorgames.com/serverless/htgserver/consts"
	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"github.com/json-iterator/go"
	lua "github.com/yuin/gopher-lua"
	luar "layeh.com/gopher-luar"
	"time"
)

type Server struct {
	*http.Server
	InternalApiClient *InternalApiClient
	HortorApiClient   *HortorApiClient
	//rpd               *contextDecoder
	//intRpd *internalContextDecoder
}

func NewServer() *Server {
	var cfg *Config
	config.LoadConfig("apiserver.json", &cfg)
	s := &Server{
		Server:            http.NewServer(cfg.Port),
		InternalApiClient: NewInternalApiClient(cfg.InternalApiUrl),
		HortorApiClient:   NewHortorApiClient(cfg.HortorApiUrl),
	}
	s.Router.Use(gin.Logger())
	//s.Router.Use(func(ctx *gin.Context) {
	//	if ctx.Request.Method == "OPTIONS" {
	//		ctx.Header("Access-Control-Expose-Headers", "Sign")
	//		ctx.Header("Access-Control-Expose-Headers", "Sign")
	//		ctx.AbortWithStatus(200)
	//		return
	//	}
	//	ctx.Next()
	//})
	//s.Router.Use(func(ctx *gin.Context) {
	//	ctx.Header("Connection", "close")
	//})
	s.Router.Use(cors.New(cors.Config{
		MaxAge:          time.Hour * 24,
		AllowAllOrigins: true,
		AllowHeaders:    []string{"Content-Type", "Sign"},
		ExposeHeaders:   []string{"Sign"},
	}))

	//s.rpd = &contextDecoder{contextWrapper: func(ctx *http.Context) http.IContext {
	//	return &Context{Context: ctx}
	//}}
	//s.intRpd = &internalContextDecoder{contextWrapper: func(ctx *http.Context) http.IContext {
	//	return &InternalContext{Context: ctx}
	//}}
	return s
}

type M map[string]interface{}

//func (gs *Server) RegisterInternalController(handlers ...http.IHttpController) {
//	gs.Server.RegisterController(gs.intRpd, handlers...)
//}

func (gs *Server) Resp(ctx *Context, resp interface{}) {
	ct := ctx.ContentType()
	if ctx.Request().Method == "GET" {
		ct = "application/x-www-form-urlencoded"
	}
	switch ct {
	case "application/x-www-form-urlencoded":
		if env.IsRelease() {
			ctx.String(200, "你真逗")
			return
		}
		changed := data.GetChanged(resp, data.GetChangedForClientFilter)
		json, _ := jsoniter.MarshalIndent(changed, "", "  ")
		ctx.String(200, string(json))
	case "application/octet-stream":
		pak := &Response{
			Body: resp,
		}
		gs.resp(ctx, pak)
	default:
		ctx.String(200, "你真逗")
		return
	}
}

func (gs *Server) RespError(ctx *Context, code int, errMsg string) {
	ct := ctx.ContentType()
	if ctx.Request().Method == "GET" {
		ct = "application/x-www-form-urlencoded"
	}
	switch ct {
	case "application/x-www-form-urlencoded":
		if env.IsRelease() {
			ctx.String(200, "text/plain", "你真逗")
			return
		}
		json, _ := jsoniter.MarshalIndent(M{"code": code, "error": errMsg}, "", "  ")
		ctx.String(200, string(json))
		return
	case "application/octet-stream":
		pak := &Response{
			Code:  int32(code),
			Error: errMsg,
		}
		gs.resp(ctx, pak)
		return
	default:
		ctx.String(200, "你真逗")
		return
	}
}

func (gs *Server) resp(ctx *Context, resp *Response) {
	resp.Time = time.Now().UnixNano() / int64(time.Millisecond)
	changed := data.GetChanged(resp, data.GetChangedForClientFilter)
	bytes := utils.BonEncodeMust(changed)
	if ctx.UseXor {
		bytes = utils.GzipXorEncodeMust(bytes)
	} else {
		bytes = utils.GzipEncodeMust(bytes)
	}
	if ctx.AntiResend {
		sess := ctx.StartRedisSession()
		defer sess.End()
		_ = session.AddResp(sess, ctx.RequestData.PlayerId, ctx.RequestData.Seq, bytes)
	}
	gs.RespRaw(ctx, bytes)
}

func (gs *Server) RespRaw(ctx *Context, bytes []byte) {
	hash := md5.New()
	hash.Write(bytes)
	hash.Write([]byte(ctx.Secret))
	sign := hash.Sum(nil)
	ctx.Header("Sign", hex.EncodeToString(sign[:]))
	ctx.Data(200, "application/octet-stream", bytes)
}

func (gs *Server) ApplyFilter(ctx *Context, req interface{}) bool {
	filter, err := Scripts().Get(ctx, scriptTypeFilter)
	if err != nil {
		gs.RespError(ctx, consts.ErrorInternalError.Code, consts.ErrorInternalError.Msg)
		ctx.Abort()
		return false
	}
	if filter == nil {
		return true
	}
	err = filter.CallByParam(lua.P{
		Fn:      filter.GetGlobal("handle"),
		NRet:    1,
		Protect: true,
	}, luar.New(filter.LState, gs), luar.New(filter.LState, ctx), luar.New(filter.LState, req))
	if err != nil {
		gs.RespError(ctx, consts.ErrorInternalError.Code, consts.ErrorInternalError.Msg)
		ctx.Abort()
		return false
	}
	rs := filter.ToBool(-1)
	filter.Pop(1)
	return rs
}

func (gs *Server) ApplyExt(ctx *Context, req interface{}) {
	filter, err := Scripts().Get(ctx, scriptTypeExt)
	if err != nil {
		gs.RespError(ctx, consts.ErrorInternalError.Code, consts.ErrorInternalError.Msg)
		ctx.Abort()
		return
	}
	if filter == nil {
		return
	}
	err = filter.CallByParam(lua.P{
		Fn:      filter.GetGlobal("handle"),
		NRet:    0,
		Protect: true,
	}, luar.New(filter.LState, gs), luar.New(filter.LState, ctx), luar.New(filter.LState, req))
	if err != nil {
		gs.RespError(ctx, consts.ErrorInternalError.Code, consts.ErrorInternalError.Msg)
		ctx.Abort()
		return
	}
}

func (gs *Server) RespInternal(ctx *InternalContext, resp interface{}) {
	ct := ctx.ContentType()
	if ctx.Request().Method == "GET" {
		ct = "application/x-www-form-urlencoded"
	}
	switch ct {
	case "application/x-www-form-urlencoded":
		if env.IsRelease() {
			ctx.String(200, "你真逗")
			return
		}
		changed := data.GetChanged(resp, data.GetChangedForClientFilter)
		json, _ := jsoniter.MarshalIndent(changed, "", "  ")
		ctx.String(200, string(json))
	case "application/octet-stream":
		pak := &InternalResponse{
			Body: resp,
		}
		//changed := data.GetChanged(pak, data.GetChangedForClientFilter)
		bytes := utils.BonEncodeMust(pak)
		ctx.Data(200, "application/octet-stream", bytes)
	default:
		ctx.String(200, "你真逗")
		return
	}
}

func (gs *Server) RespErrorInternal(ctx *InternalContext, code int, errMsg string) {
	ct := ctx.ContentType()
	if ctx.Request().Method == "GET" {
		ct = "application/x-www-form-urlencoded"
	}
	switch ct {
	case "application/x-www-form-urlencoded":
		if env.IsRelease() {
			ctx.String(200, "text/plain", "你真逗")
			return
		}
		json, _ := jsoniter.MarshalIndent(M{"code": code, "error": errMsg}, "", "  ")
		ctx.String(200, string(json))
		return
	case "application/octet-stream":
		pak := &Response{
			Code:  int32(code),
			Error: errMsg,
		}
		bytes := utils.BonEncodeMust(pak)
		ctx.Data(200, "application/octet-stream", bytes)
		return
	default:
		ctx.String(200, "你真逗")
		return
	}
}
